package com.event.starter.listener;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.event.common.annotation.RepoNameSpace;
import com.event.common.exception.UpdateEventException;
import com.event.common.model.ClassAnnotationEvent;
import com.event.common.model.UpdateAfterEvent;
import com.event.common.model.UpdateBeforeEvent;
import com.event.core.context.AnnotationContext;
import com.event.core.context.EventContext;
import com.event.starter.util.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import javax.persistence.*;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
 * @Description 命名空间监听器
 * @Author Bing
 * @Date 2024/10/9 11:39
 */
@Slf4j
public class JpaNameSpaceListener {

    @PrePersist
    public void prePersist(Object entity) {
        preEvent(entity);
    }

    @PostPersist
    public void postPersist(Object entity) {
        postEvent(entity);
    }

    @PreUpdate
    public void preUpdate(Object entity) {
        preEvent(entity);
    }

    @PostUpdate
    public void postUpdate(Object entity) {
        postEvent(entity);
    }

    @PreRemove
    public void preRemove(Object entity) {
        preEvent(entity);
    }

    @PostRemove
    public void postRemove(Object entity) {
        postEvent(entity);
    }

    private void preEvent(Object entity){
        try {
            FuncParam funcParam = FuncParam.of(entity);
            UpdateBeforeEvent updateBeforeEvent = handleUpdateBeforeEvent(funcParam);
            if(ObjectUtil.isNull(updateBeforeEvent)){
                return;
            }
            funcParam.getEventContext().updateBeforeEvent(updateBeforeEvent);
        } catch (Exception e){
            log.error("jpa preEvent error", e);
        }
    }

    private void postEvent(Object entity){
        try {
            FuncParam funcParam = FuncParam.of(entity);
            UpdateBeforeEvent beforeEvent = handleUpdateBeforeEvent(funcParam);
            if(ObjectUtil.isNull(beforeEvent)){
                return;
            }
            UpdateAfterEvent afterEvent = new UpdateAfterEvent();
            BeanUtil.copyProperties(beforeEvent, afterEvent);
            funcParam.getEventContext().updateAfterEvent(afterEvent);
        } catch (Exception e){
            log.error("jpa postEvent error", e);
        }
    }

    private static class FuncParam{

        private Object entity;

        private AnnotationContext annotationContext;
        // 事件上下文
        private EventContext eventContext;
        // 关注的类注解列表
        private List<ClassAnnotationEvent> classAnnotationEvents;

        private FuncParam(AnnotationContext annotationContext,EventContext eventContext,Object entity){
            this.annotationContext = annotationContext;
            this.eventContext = eventContext;
            this.entity = entity;
            this.classAnnotationEvents = annotationContext.getClassAnnotationEvents(predicate -> predicate.getAnnotation().annotationType().equals(RepoNameSpace.class));
        }

        public Object getEntity() {
            return entity;
        }

        public AnnotationContext getAnnotationContext() {
            return annotationContext;
        }

        public EventContext getEventContext() {
            return eventContext;
        }

        public List<ClassAnnotationEvent> getClassAnnotationEvents() {
            return classAnnotationEvents;
        }

        public static FuncParam of(Object entity){
            AnnotationContext annotationContext = SpringUtils.getBean(AnnotationContext.class);
            EventContext eventContext = SpringUtils.getBean(EventContext.class);
            return new FuncParam(annotationContext, eventContext,entity);
        }
    }

    /**
     * 处理更新事件
     * @param funcParam
     * @return
     */
    private UpdateBeforeEvent handleUpdateBeforeEvent(FuncParam funcParam){
        RepoNameSpace repoNameSpace = findRepoNameSpace(funcParam);
        if(ObjectUtil.isNull(repoNameSpace)){
            return null;
        }
        Field field = getIdField(repoNameSpace.modelClass());
        if(ObjectUtil.isNull(field)){
            return null;
        }
        UpdateBeforeEvent updateBeforeEvent = new UpdateBeforeEvent();
        updateBeforeEvent.setAlias(repoNameSpace.alias());
        updateBeforeEvent.setMapperClass(repoNameSpace.getClass());
        updateBeforeEvent.setModelClass(repoNameSpace.modelClass());
        updateBeforeEvent.setNamespace(repoNameSpace.namespace());
        updateBeforeEvent.setIdName(field.getName());
        GeneratedValue generatedValue = field.getAnnotation(GeneratedValue.class);
        int autoIncr = 0;
        if(ObjectUtil.isNotNull(generatedValue)){
            autoIncr = generatedValue.strategy().equals(GenerationType.IDENTITY) ? 1 : 0;
        }
        updateBeforeEvent.setAutoIncr(autoIncr);
        Optional.ofNullable(ReflectUtil.getFieldValue(funcParam.getEntity(), field.getName())).ifPresent(item ->{
            updateBeforeEvent.setIdValue(item.toString());
        });
        return updateBeforeEvent;
    }

    /**
     * 查找命名空间
     * @param funcParam
     * @return
     */
    private RepoNameSpace findRepoNameSpace(FuncParam funcParam){
        if(CollUtil.isEmpty(funcParam.getClassAnnotationEvents())){
            return null;
        }
        Optional<RepoNameSpace> optional = funcParam.getClassAnnotationEvents().stream()
                .map(item ->(RepoNameSpace)item.getAnnotation())
                .filter(item -> item.modelClass().equals(funcParam.getEntity().getClass()))
                .findFirst();
        if(!optional.isPresent()){
            return null;
        }
        return optional.get();
    }

    /**
     * 获取id字段
     * @param mapperClass
     * @return
     */
    private Field getIdField(Class<?> mapperClass){
        Optional<Field> optional = Arrays.stream(ReflectUtil.getFields(mapperClass)).filter(field -> field.isAnnotationPresent(Id.class)).findFirst();
        if(!optional.isPresent()){
            throw new UpdateEventException("Not Field has Id");
        }
        return optional.get();
    }
}